package sgf;

import java.awt.*;
import java.util.ArrayList;

import javax.swing.*;

import ui.GobanPanel;
import ui.IGobanListener;
import ui.Main;
import ui.TextEditorPane;
import ui.WidgetPanel;
import util.GS;
import util.Util;

import board.Board;

/**
 * Controls navigation within a problem represented by an SGF file
 * @author TKington
 *
 */
public class SGFController implements IGobanListener {
	/** Play mode */
    public static final int PLAY = 0;
    /** Navigate solution mode */
    public static final int NAV_SOLN = 1;
    
    /** The one and only instance. */
    private static SGFController instance;
    
    /** The board. */
    private Board board;
    /** The GobanPanel. */
    private GobanPanel panel;
    
    /** The root node of the current problem. */
    private SGFNode root;
    /** The current node. */
    private SGFNode curNode;
    /** The WidgetPanel. */
    private WidgetPanel widgetPanel;
    /** The TextEditorPane for displaying comments. */
    private TextEditorPane commentArea;
    /** The current mode - play or navigate solution. */
    private int mode;
    /** The color played by the human for this problem. */
    private int playerColor;
    /** The color played at the current node. */
    private int curColor;
    
    /** Used to disable events during delay before computer response. */
    private boolean enabled = true;
    
    /** Creates the one and only instance. */
    private SGFController() { /* */ }
    
    /**
     * Returns the SGFController instance.
     * @return the SGFController instance
     */
    public static SGFController instance() {
    	if(instance == null)
    		instance = new SGFController();
    	return instance;
    }
    
    /**
     * Starts a new problem.
     * @param sgf the sgf representation of the problem.
     * @return true if the problem was started successfully
     */
    public boolean startProblem(String sgf) {
        try {
            ArrayList recs = SGFParser.parse(sgf);
            root = (SGFNode)recs.get(0);
            
            int size = root.getSize();
            if(size != 0)
                board = new Board(size);
            else board = new Board(19);
            
            root.validatePoints(board.getSize());
        }
        catch(OutOfMemoryError e) {
            JOptionPane.showMessageDialog(null, "Out of memory while loading problem");
            return false;
        }
        catch(SGFParseException e) {
            String msg = e.getMessage();
            if(msg.length() > 50)
                msg = msg.substring(0, 50) + "...";
            JOptionPane.showMessageDialog(panel, "Error parsing problem:\n" + msg);
            Util.logSilent(e);
            return false;
        }
        catch(Exception e) {
            JOptionPane.showMessageDialog(panel, "Error parsing problem");
            Util.logSilent(e);
            return false;
        }

        panel.setBoard(board);
        board.setPanel(panel);

        if(!root.markPathsRIGHT())
            root.markPathsWV();
        
        Rectangle bounds = root.getBounds();
        
        //	If there's nothing on the board, show the whole thing
        if(bounds == null)
        	bounds = new Rectangle(0, 0, board.getSize() - 1, board.getSize() - 1);
        
        //  GobanPanel expects 1-based width & height
        bounds.width++;
        bounds.height++;
        panel.setSize(bounds, null);
        
        //  Process nodes until we come to one with moves as children
        root.execute(board);
        root.updateState(board);
        commentArea.setText(root.getComment());
        while(!root.hasChildMoves()) {
            SGFNode newRoot = root.getFirstChild();
            if(newRoot == null)
                break;
            
            root = newRoot;
            root.setParent(null);
            root.execute(board);
            root.updateState(board);
            commentArea.setText(root.getComment());
        }
        
        setCurColor(playerColor = root.getNextPlayer());
        widgetPanel.clearSolved();
        root.setPlayer(playerColor * -1);
        
        curNode = root;
        
        panel.hideGhost();
        return true;
    }
    
    /**
     * Handles a mouse click on the board.
     * @param x the x location
     * @param y the y location
     * @param modifiers the MouseEvent modifiers
     */
    public void mouseClicked(int x, int y, int modifiers)
    {
        switch(mode)
        {
            case PLAY:
                playMouseClicked(x, y);
                break;
            case NAV_SOLN:
                navSolnMouseClicked(x, y);
                break;
        }
    }
    
    /**
     * Handles a mouse wheel event by moving backwards through the problem.
     * @param numClicks how far to move
     */
    public void mouseWheelMoved(int numClicks) {
    	numClicks *= -1;
    	for(int i = 0; i < numClicks; i++)
    		goBack();
    }
    
    /**
     * Handles a mouse click in play mode by playing a stone, if 
     * a legal move was chosen, and then playing the computer
     * response, if any.
     * @param x the x location
     * @param y the y location
     */
    private void playMouseClicked(int x, int y) {
        if(!enabled)
            return;
        
        if(!board.isLegalMove(curColor, x, y)) {
            Toolkit.getDefaultToolkit().beep();
            return;
        }
        
        SGFNode next = curNode.getNextNode(new Point(x,y));
        
        if(next == null) {
            next = new SGFNode(curNode, x, y);
            curNode.addChild(next);
        }
        
        doNode(next);
        
        SGFNode resp = next.getResponse();
        if(resp != null) {
            enabled = false;
            SwingUtilities.invokeLater(new Responder(resp));
        }
    }
    
    /**
     * Makes the move described by the passed SGFNode, and marks
     * the problem right or wrong, if necessary.
     * @param n the SGFNode describing the move
     */
    private void doNode(SGFNode n) {
        if(GS.getClickSoundEnabled())
            Main.clickSound.play();
        
        if(n.isRight()) {
            widgetPanel.setSolved(true);
        }
        else {
            if(!n.hasChildrenFromFile()) {
                widgetPanel.setSolved(false);
            }
            else widgetPanel.clearSolved();
        }

        n.execute(board);
        n.updateState(board);
        commentArea.setText(n.getComment());
        curNode = n;
        setCurColor(curColor *= -1);
    }
    
    /**
     * Handles a mouse click in navigate solution mode by moving to
     * the selected next move, or creating one if it does not exist.
     * @param x the x location
     * @param y the y location
     */
    private void navSolnMouseClicked(int x, int y) {
        SGFNode next = curNode.getNextNode(new Point(x,y));
        
        if(next == null) {
            if(!board.isLegalMove(curNode.getPlayer() * -1, x, y)) {
                Toolkit.getDefaultToolkit().beep();
                return;
            }
            
            next = new SGFNode(curNode, x, y);
            curNode.addChild(next);
        }
        
        doNode(next);
    }
    
    /**
     * Moves back one move in the problem.
     *
     */
    public void goBack()
    {
        do {
            SGFNode parent = curNode.getParent();
            if(parent == null) {
                Toolkit.getDefaultToolkit().beep();
                return;
            }

            board.undoLast();
            curNode = parent;
            setCurColor(curColor *= -1);
            curNode.updateState(board);
            commentArea.setText(curNode.getComment());
            
            widgetPanel.clearSolved();
            
            if(mode == NAV_SOLN || !curNode.isFromFile())
                break;
        } while(curNode.getPlayer() == playerColor);
    }
    
    /**
     * Restarts the current problem.
     *
     */
    public void restart()
    {
        mode = PLAY;
        panel.setNavMode(false);
        widgetPanel.clearSolved();
        SGFNode parent;
        while((parent = curNode.getParent()) != null) {
            board.undoLast();
            curNode = parent;
            setCurColor(curColor *= -1);
            curNode.updateState(board);
            commentArea.setText(curNode.getComment());
        }
    }
    
    /**
     * Restarts theh problem, and switches to navigate solution mode.
     *
     */
    public void navigateSolution() {
        mode = NAV_SOLN;
        panel.setNavMode(true);
        
        widgetPanel.clearSolved();
        SGFNode parent;
        while((parent = curNode.getParent()) != null) {
            board.undoLast();
            curNode = parent;
            setCurColor(curColor *= -1);
            curNode.updateState(board);
            commentArea.setText(curNode.getComment());
        }
    }
    
    /**
     * Associates this SGFController with a comment area.
     * @param a the comment area
     */
    public void setCommentArea(TextEditorPane a) { commentArea = a; }
    
    /**
     * Associates this SGFController with a WidgetPanel.
     * @param p the WidgetPanel
     */
    public void setWidgetPanel(WidgetPanel p) { widgetPanel = p; }
    
    /**
     * Sets the color of the next stone to be played.
     * @param c the color - 1 for black, -1 for white
     */
    private void setCurColor(int c) {
        curColor = c;
        widgetPanel.setToPlay(curColor);
        panel.setGhostColor(curColor);
    }
    
    /**
     * This class waits for a small amount of time, and then plays
     * the computer's response to the player's last move.
     *
     */
    class Responder implements Runnable{
    	/** The computer's response. */
        SGFNode node;
        
        /**
         * Creates a Responder instance.
         * @param n the move to be played as the computer's response
         */
        Responder(SGFNode n) { this.node = n; }
        
        public void run() {
            try { Thread.sleep(250); } catch(Exception e) { /* */ }
            doNode(node);
            enabled = true;
        }
    }
    
    /**
     * Sets the GobanPanel this SGFController is associated with.
     * @param p the GobanPanel
     */
    public void setPanel(GobanPanel p) { panel = p; }
    
    /**
     * Sets the Board this SGFController is associated with.
     * @param b the Board
     */
    public void setBoard(Board b) { board = b; }
}
